﻿using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Media.Animation;
using System.Windows.Media.Effects;
using System.Windows.Media.Media3D;
using System.Windows.Controls;
using System.Windows.Shapes;
using System.Windows.Controls.Primitives;
using Bling.Properties;
using Bling.DSL;
using Bling.Shaders;
using Bling.Linq;
using Bling.Util;
using linq = Microsoft.Linq.Expressions;

namespace Bling.Shaders {
  public class PersistentTexture {
    private byte[] ReadBuffer;
    private byte[] WriteBuffer;
    private readonly int Stride;
    public readonly int PixelWidth;
    public readonly int PixelHeight;

    public readonly WriteableBitmap Bitmap;
    private Microsoft.Action<byte[], byte[]> Transfer0;
    public Func<BrushBl, PointBl, ColorBl> Transfer {
      set {
        var i0 = new CoreExpressionValue<int>("i");
        var j0 = new CoreExpressionValue<int>("j");
        IntBl i = i0;
        IntBl j = j0;
        var colorV = linq.Expression.Parameter(typeof(Color), "color");
        var offsetV = linq.Expression.Parameter(typeof(int), "offset");

        var UV = new PointBl(i * 1d.Bl() / PixelWidth, j * 1d.Bl() / PixelHeight);
        var eval = new BitmapEval(new ParameterContext()) { Texture = this };
        var clr = value(eval.Input, UV);
        CoreExpressionValue<byte[]> WriteBytes = new CoreExpressionValue<byte[]>("WriteBytes");



        var color = ((BitmapEval.MyInfo<Color>)clr.Underlying.Eval(eval)).Value;
        var offset = ((BitmapEval.MyInfo<int>)Offset(UV).Underlying.Eval(eval)).Value;
        var props = new string[] { "B", "G", "R", "A" };

        eval.Instructions.Add(linq.Expression.Assign(colorV, color));
        eval.Instructions.Add(linq.Expression.Assign(offsetV, offset));

        for (int k = 0; k < 4; k++) {
          var lhs = linq.Expression.ArrayAccess(WriteBytes.Value, linq.Expression.Add(offset, linq.Expression.Constant(k)));
          var rhs = linq.Expression.Property(color, props[k]);
          eval.Instructions.Add(linq.Expression.Assign(lhs, rhs));
        }
        throw new NotSupportedException();
        /*
        Func<linq.ParameterExpression, int, linq.Expression, linq.Block> mkLoop = (ii, sz, bod) => {
          var init = linq.Expression.Assign(ii, linq.Expression.Constant(0));
          var loop = linq.Expression.Loop(linq.Expression.LessThan(ii, linq.Expression.Constant(sz)),
                               linq.Expression.Assign(ii, linq.Expression.Add(ii, linq.Expression.Constant(1))),
                               bod, null, null, null);
          return linq.Expression.Block(init, loop);
        };
        var body = mkLoop(i0.Value, PixelWidth, 
          mkLoop(j0.Value, PixelHeight, linq.Expression.Block(eval.Instructions)));
        var scope = linq.Expression.Scope(body, eval.Temps.Values);
        scope = linq.Expression.Scope(scope, i0.Value, j0.Value, colorV, offsetV);

        //linq.Expression.Scope(body, 
        var func = linq.Expression.Lambda(scope, eval.ReadBytes.Value, WriteBytes.Value);
        Transfer0 = (Microsoft.Action<byte[],byte[]>) func.Compile();
         */
      }
    }



    private class BitmapEval : LinqEval<BitmapEval> {
      public PersistentTexture Texture;
      public readonly CoreExpressionValue<Brush> Input = new CoreExpressionValue<Brush>("Input");
      public readonly CoreExpressionValue<byte[]> ReadBytes = new CoreExpressionValue<byte[]>("ReadByte");
      public BitmapEval(ParameterContext Slots) : base(Slots) {}
      public override Eval<BitmapEval>.Info<U> Operate<S, T, U>(Operation<S, T, U> op) {
        if (op.Op.Marker == Markers.Tex2DMarker) {
          var brush = ((MyInfo<Brush>)(object)op.Value1.Eval(this)).Value;
          if (brush != Input.Value) throw new NotSupportedException();
          // implement a read. 
          PointBl P = (Value<Point>)(object)op.Value2;
          // clamp!
          IntBl Offset = Texture.Offset(P);
          Offset = Offset.Clamp(0, Texture.ReadBuffer.Length - 4);
          var offset = ((MyInfo<int>)Offset.Underlying.Eval(this)).Value;
          linq.Expression[] clr = new linq.Expression[4];
          for (int i = 0; i < 4; i++) {
            clr[i] = linq.Expression.ArrayAccess(ReadBytes.Value, 
              linq.Expression.Add(offset, linq.Expression.Constant(i)));
          }
          var result = linq.Expression.Call(typeof(Color), "FromArgb", new Type[] { }, clr[3], clr[2], clr[1], clr[0]);
          return new MyInfo<U>() { Value = result };
        }
        return base.Operate<S, T, U>(op);
      }

    }
    public PersistentTexture(Point Size) {
      Bitmap = new WriteableBitmap((int)Size.X, (int)Size.Y, 96, 96, PixelFormats.Bgra32, null);
      Stride = Bitmap.BackBufferStride;
      int ArraySize = Stride * Bitmap.PixelHeight;
      ReadBuffer = new byte[ArraySize];
      WriteBuffer = new byte[ArraySize];
      // initialize.
      this.PixelHeight = Bitmap.PixelHeight;
      this.PixelWidth = Bitmap.PixelWidth;
      for (int i = 0; i < PixelWidth; i++)
        for (int j = 0; j < PixelHeight; j++)
          this[i, j] = Colors.White;
      Clean();

      // just a simple copy!
      this.Transfer = (input, uv) => input[uv];
    }
    public void Clean() { // XXX: call from UI thread.
     {
        var rect0 = new Int32Rect(0, 0, PixelWidth, PixelHeight);
        Bitmap.WritePixels(rect0, WriteBuffer, Stride, 0);
        // swap buffers at last.
        var temp = WriteBuffer;
        WriteBuffer = ReadBuffer;
        ReadBuffer = temp;
      }
    }
    private IntBl Offset(PointBl p) {
      IntBl x = (IntBl)(p.X * PixelWidth).Round;
      IntBl y = (IntBl)(p.Y * PixelHeight).Round;
      return Offset(x, y);
    }

    private int Offset(Point p) {
      int x = (int)Math.Round(p.X * PixelWidth);
      int y = (int)Math.Round(p.Y * PixelHeight);
      return Offset(x, y);
    }
    private int Offset(int x, int y) {
      return (x * 4) + (y * Stride);
    }
    private IntBl Offset(IntBl x, IntBl y) {
      return (x * 4) + (y * Stride);
    }
    public void DoTransfer() {
      Transfer0(ReadBuffer, WriteBuffer);
    }
    public Color DefaultColor = Colors.White;

    public Color this[Point p] {
      get {
        if (p.X < 0 || p.X > 1 || p.Y < 0 || p.Y > 1) return DefaultColor;

        var offset = Offset(p);
        var b = ReadBuffer[offset + 0];
        var g = ReadBuffer[offset + 1];
        var r = ReadBuffer[offset + 2];
        var a = ReadBuffer[offset + 3];
        return Color.FromArgb(a, r, g, b);
      }
      set {
        if (p.X < 0 || p.X > 1 || p.Y < 0 || p.Y > 1) return;

        var offset = Offset(p);
        WriteBuffer[offset + 0] = value.B;
        WriteBuffer[offset + 1] = value.G;
        WriteBuffer[offset + 2] = value.R;
        WriteBuffer[offset + 3] = value.A;
      }
    }
    public void DrawRect(Point start, Point size, Color color) {
      for (int x = 0; x < size.X; x++)
        for (int y = 0; y < size.Y; y++) {
          this[(int)Math.Round(start.X + x), (int)Math.Round(start.Y + y)] = color;
        }
    }

    public void DrawLine(Func<double, Point> line, Func<double, Color> color) {
      var start = line(0);
      var end = line(1);
      var diff = end - (Vector)start;
      var I = (int)Math.Round(Math.Abs(diff.X));
      I = Math.Max(I, (int)Math.Round(Math.Abs(diff.Y)));
      var di = 1d / I;
      var ddi = 0d;
      for (int i = 0; i < I; i++) {
        this[line(di)] = color(di);

        ddi += di;
      }


    }
    public Color this[int x, int y] {
      get {
        if (x < 0 || x >= PixelWidth || y < 0 || y >= PixelHeight) return DefaultColor;
        var offset = Offset(x,y);
        var b = ReadBuffer[offset + 0];
        var g = ReadBuffer[offset + 1];
        var r = ReadBuffer[offset + 2];
        var a = ReadBuffer[offset + 3];
        return Color.FromArgb(a, r, g, b);
      }
      set {
        if (x < 0 || x >= PixelWidth || y < 0 || y >= PixelHeight) return;
        var offset = Offset(x, y);
        WriteBuffer[offset + 0] = value.B;
        WriteBuffer[offset + 1] = value.G;
        WriteBuffer[offset + 2] = value.R;
        WriteBuffer[offset + 3] = value.A;
      }
    }
  }

}